前一天講到合約(Contracts)和啟動器(Launcher)取代StartActivityForResult,官方也幫我們建立了14種常見的合約模板,以下是官方的14種合約
ActivityResultContracts.CreateDocument()
ActivityResultContracts.GetContent()
ActivityResultContracts.GetMultipleContents()
ActivityResultContracts.OpenDocument()
ActivityResultContracts.OpenDocumentTree()
ActivityResultContracts.OpenMultipleDocuments()
ActivityResultContracts.PickContact()
ActivityResultContracts.RequestMultiplePermissions()
ActivityResultContracts.RequestPermission()
ActivityResultContracts.StartActivityForResult()
ActivityResultContracts.StartIntentSenderForResult()
ActivityResultContracts.TakePicture()
ActivityResultContracts.TakePicturePreview()
ActivityResultContracts.TakeVideo()
這篇先介紹
An ActivityResultContract to prompt the user to select a path for creating a new document, returning the content: Uri of the item that was created.
The input is the suggested name for the new file.This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
CreateDocument()
可以用在拍照前的建立空白檔案。
以下範例是建立空白的saberEat.jpg
檔案,然後回傳檔案的content://uri
,需要注意使用者可以更改檔案名稱。
createDocumentResultLauncher.launch("saberEat.jpg")
private val createDocumentResultLauncher =
registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
Log.d("maho", "回傳: $uri")
}
實際執行程式後的Log
D/maho: 回傳: content://com.android.providers.downloads.documents/document/1692
An ActivityResultContract to prompt the user to pick a piece of content, receiving a content:// Uri for that content that allows you to use android.content.ContentResolver.openInputStream(Uri) to access the raw data. By default, this adds Intent.CATEGORY_OPENABLE to only return content that can be represented as a stream.
The input is the mime type to filter by, e.g. image/*.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
以下範例是輸入一個MIME type
指定類型,然後選擇一個檔案,回傳檔案的content://uri
,不可以輸入null
。
getContentResultLauncher.launch("image/*")
private val getContentResultLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
Log.d("maho", "回傳: $uri")
}
實際執行程式後的Log
D/maho: 回傳: content://com.android.providers.downloads.documents/document/1688
An ActivityResultContract to prompt the user to pick one or more a pieces of content, receiving a content:// Uri for each piece of content that allows you to use android.content.ContentResolver.openInputStream(Uri) to access the raw data. By default, this adds Intent.CATEGORY_OPENABLE to only return content that can be represented as a stream.
The input is the mime type to filter by, e.g. image/*.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
以下範例是輸入一個MIME type
指定類型,然後長按選擇多個檔案,用陣列的形式回傳檔案的content://uri
,不可以輸入null
。
getMultipleContentsResultLauncher.launch("image/*")
private val getMultipleContentsResultLauncher =
registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { uri ->
Log.d("maho", "回傳: $uri")
}
實際執行程式後的Log
D/maho: 回傳: [
content://com.android.providers.downloads.documents/document/1688,
content://com.android.providers.downloads.documents/document/1677
]
An ActivityResultContract to prompt the user to open a document, receiving its contents as a file:/http:/content: Uri.
The input is the mime types to filter by, e.g. image/*.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
See Also: DocumentsContract
以下範例是輸入多個MIME type
指定類型,然後選擇一個檔案,回傳檔案的content://uri
,可以輸入null
表示不指定類型。
openDocumentResultLauncher.launch(arrayOf("image/jpeg", "video/mp4"))
private val openDocumentResultLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
Log.d("maho", "回傳: $uri")
}
實際執行程式後的Log
D/maho: 回傳: content://com.android.providers.downloads.documents/document/1677
An ActivityResultContract to prompt the user to select a directory, returning the user selection as a Uri. Apps can fully manage documents within the returned directory.
The input is an optional Uri of the initial starting location.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
See Also:
Intent.ACTION_OPEN_DOCUMENT_TREE, DocumentsContract.buildDocumentUriUsingTree, DocumentsContract.buildChildDocumentsUriUsingTree
以下範例是選擇資料夾,然後回傳資料夾的content://uri
,可以輸入null
表示不指定路徑。
openDocumentTreeResultLauncher.launch(null)
private val openDocumentTreeResultLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
Log.d("maho", "回傳: $uri")
}
選擇Download/Duo
資料夾,實際執行程式後的Log
D/maho: 回傳: content://com.android.externalstorage.documents/tree/primary%3ADownload%2FDuo
An ActivityResultContract to prompt the user to open (possibly multiple) documents, receiving their contents as file:/http:/content: Uris.
The input is the mime types to filter by, e.g. image/*.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
See Also:
DocumentsContract
以下範例是輸入多個MIME type
指定類型,然後選擇多個檔案,用陣列的形式回傳檔案的content://uri
,不可以輸入null
。
openMultipleDocumentsResultLauncher.launch(arrayOf("image/jpeg", "video/mp4"))
private val openMultipleDocumentsResultLauncher =
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { list ->
Log.d("maho", "回傳: $list")
}
實際執行程式後的Log
D/maho: 回傳: [
content://com.android.providers.media.documents/document/image%3A5789,
content://com.android.providers.media.documents/document/image%3A5791
]
An ActivityResultContract to request the user to pick a contact from the contacts app.
The result is a content: Uri.
See Also:
ContactsContract
選擇單個連絡人,回傳content://uri
pickContactResultLauncher.launch(null)
private val pickContactResultLauncher =
registerForActivityResult(ActivityResultContracts.PickContact()) { uri ->
Log.d("maho", "回傳: $uri")
}
實際執行程式後的Log
D/maho: 回傳: content://com.android.contacts/contacts/lookup/1519iaad44c18aa14d9a/10
An Intent action for making a permission request via a regular Activity.startActivityForResult API. Caller must provide a String[] extra EXTRA_PERMISSIONS Result will be delivered via Activity.onActivityResult(int, int, Intent) with String[] EXTRA_PERMISSIONS and int[] EXTRA_PERMISSION_GRANT_RESULTS, similar to Activity.onRequestPermissionsResult(int, String[], int[])
See Also:
Activity.requestPermissions(String[], int), Activity.onRequestPermissionsResult(int, String[], int[])
Key for the extra containing all the requested permissions.
See Also:
ACTION_REQUEST_PERMISSIONS
Key for the extra containing whether permissions were granted.
See Also:
ACTION_REQUEST_PERMISSIONS
以下範例是輸入多個系統權限,用map的形式回傳每個權限的true
和false
,可以輸入null
,不過沒意義。
requestMultiplePermissionsResultLauncher.launch(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
private val requestMultiplePermissionsResultLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { map ->
Log.d("maho", "回傳: $map")
}
實際執行程式後的Log
D/maho: 回傳: {
android.permission.CAMERA=false,
android.permission.WRITE_EXTERNAL_STORAGE=false
}
An ActivityResultContract to request a permission
以下範例是輸入一個系統權限,回傳權限的true
和false
,可以輸入null
,不過沒意義。
requestPermissionResultLauncher.launch(Manifest.permission.CAMERA)
private val requestPermissionResultLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { boolean ->
Log.d("maho", "回傳: $boolean")
}
實際執行程式後的Log
D/maho: 回傳: false
An ActivityResultContract that calls Activity.startIntentSender(IntentSender, Intent, int, int, int). This ActivityResultContract takes an IntentSenderRequest, which must be constructed using an IntentSenderRequest.Builder. If the call to Activity.startIntentSenderForResult(IntentSender, int, Intent, int, int, int) throws an IntentSender.SendIntentException the androidx.activity.result.ActivityResultCallback will receive an ActivityResult with an Activity.RESULT_CANCELED resultCode and whose intent has the action of ACTION_INTENT_SENDER_REQUEST and an extra EXTRA_SEND_INTENT_EXCEPTION that contains the thrown exception.
我不知道這個是做什麼用的,Google後才知道是Phone Selector Api,可以用來做簡訊驗證之類的功能。
//implementation 'com.google.android.gms:play-services-auth:19.2.0'
val hintRequest = HintRequest
.Builder()
.setPhoneNumberIdentifierSupported(true)
.build()
val credentialsOptions = CredentialsOptions
.Builder()
.forceEnableSaveDialog()
.build()
val credentials = Credentials
.getClient(this, credentialsOptions)
.getHintPickerIntent(hintRequest)
startIntentSenderForResultResultLauncher.launch(
IntentSenderRequest
.Builder(credentials)
.build()
)
private val startIntentSenderForResultResultLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
if (RESULT_OK == result.resultCode) {
val credential: Credential? = result
.data
?.getParcelableExtra(Credential.EXTRA_KEY)
Log.d(
"maho",
"id: ${credential?.id} " +
"\naccountType: ${credential?.accountType} " +
"\nfamilyName: ${credential?.familyName} " +
"ngivenName: ${credential?.givenName} " +
"\nidTokens: ${credential?.idTokens} " +
"\nname: ${credential?.name} " +
"\npassword: ${credential?.password} " +
"\nprofilePictureUri: ${credential?.profilePictureUri}"
)
}
}
實際執行程式後的Log
D/maho: id: +886910123456
accountType: null
familyName: null
givenName: null
idTokens: []
name: null
password: null
profilePictureUri: null
An ActivityResultContract to take a picture saving it into the provided content-Uri.
Returns true if the image was saved into the given Uri.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
以下範例是建立空白檔案後再開啟相機拍攝照片,回傳true
表示儲存成功,false
表示儲存失敗。
第一種寫法:用ActivityResultContracts.CreateDocument()
建立檔案,取得檔案的uri
後再使用TakePicture()
拍照
takePictureCreateDocumentResultLauncher.launch("002.jpg")
private val takePictureCreateDocumentResultLauncher =
registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
takePictureResultLauncher.launch(uri)
}
private val takePictureResultLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { boolean ->
Log.d("maho", "回傳: $boolean")
}
實際執行程式後的Log
//ActivityResultContracts.CreateDocument()的Log
D/maho: 回傳: content://com.android.providers.downloads.documents/document/1701
//ActivityResultContracts.TakePicture()的Log
D/maho: 回傳: true
第二種寫法:使用File()
建立檔案,再使用getUriForFile()
取得檔案的uri
後再使用TakePicture()
拍照
val picturePath = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "003.jpg")
val uri = getUriForFile(this, "$packageName.fileprovider", picturePath)
takePictureResultLauncher.launch(uri)
private val takePictureResultLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { boolean ->
Log.d("maho", "回傳: $boolean")
}
如果要用第二種寫法還要前置作業
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="lens_picture"
path="." />
</paths>
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/tool_provider_paths" />
</provider>
</application>
</manifest>
實際執行程式後的Log
D/maho: 回傳: true
An ActivityResultContract to take small a picture preview, returning it as a Bitmap.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
以下範例是開啟相機,拍攝照片後回傳相片縮圖的bitmap
,因為是縮圖,所以圖片會非常小,官方文件表示 take small a picture preview,所以我也不太懂這個可以用來做什麼功能。
takePicturePreviewResultLauncher.launch(null)
private val takePicturePreviewResultLauncher =
registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->
amIvTakePicturePreview.setImageBitmap(bitmap)
//把縮圖存起來的程式碼
val picturePath = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "004.jpg")
val fileOutputStream = FileOutputStream(picturePath)
val bufferedOutputStream = BufferedOutputStream(fileOutputStream)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bufferedOutputStream)
bufferedOutputStream.flush()
bufferedOutputStream.close()
}
實際執行程式後的Log
D/maho: 回傳: android.graphics.Bitmap@93f6ede
An ActivityResultContract to take a video saving it into the provided content-Uri.
Returns a thumbnail.This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().
以下範例是建立空白檔案後再開啟相機錄製影片,錄製完後回傳影片縮圖的bitmap
。
//路徑為空,會儲存到 DCIM 資料夾
takeVideoResultLauncher.launch(null)
//指定儲存路徑
val picturePath = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "005.mp4")
val uri = getUriForFile(this, "$packageName.fileprovider", picturePath)
takeVideoResultLauncher.launch(uri)
private val takeVideoResultLauncher =
registerForActivityResult(ActivityResultContracts.TakeVideo()) { bitmap ->
amIvTakeVideo.setImageBitmap(bitmap)
}
實際執行程式後的Log,影片有儲存成功,理論上要回傳影片縮圖的bitmap
,但我在Android 11
一直試不出來,StackOverflow也有一樣的問題,也還沒解決,只能說是官方的坑
D/maho: 回傳: null
我自己覺得官方合約的命名方式滿混亂的,開啟檔案就有Get
和Open
,然後檔案本身也有Document
和Content
,所以來分類一下
輸入單個檔案類型 | 輸入多個檔案類型 | |
---|---|---|
回傳單個檔案uri | GetContent() | OpenDocument() |
回傳陣列檔案uri | GetMultipleContents() | OpenMultipleDocuments() |
輸入單個權限 | 輸入多個權限 | |
---|---|---|
回傳單個權限驗證結果 | RequestPermission() | X |
回傳陣列權限驗證結果 | X | RequestMultiplePermissions() |
相片 | 相片縮圖 | 影片 | |
---|---|---|---|
回傳布林值 | TakePicture() | X | X |
回傳相片縮圖bitmap | X | TakePicturePreview() | X |
回傳影片縮圖bitmap | X | X | TakeVideo() |
程式碼放在feature/resultTemplate
分支
https://github.com/AndyAWD/AndroidSystem/tree/feature/resultTemplate